﻿Shader "Kun URP/Test/Tessellation"
{
    Properties
    {
        _WireframeColor ("Wireframe Color", Color) = (0, 0, 0, 1)
        _WireframeSmoothing ("Wireframe Smoothing", Range(0, 10)) = 1
		_WireframeThickness ("Wireframe Thickness", Range(0, 10)) = 1
        _TessellationEdgeLength ("Tessellation Edge Length", Range(5, 100)) = 50
    }
    SubShader
    {
        Tags
        {
            "RenderPipeline"="UniversalRenderPipeline"
        }

        HLSLINCLUDE
        ENDHLSL

        Pass
        {
            HLSLPROGRAM

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            #pragma vertex Vert
            #pragma hull Hull
            #pragma domain Domain
            #pragma geometry Geom
            #pragma fragment Frag
            
            float4 _WireframeColor;
            float _WireframeSmoothing;
            float _WireframeThickness;
            float _TessellationEdgeLength;

            struct Attributes
            {
                float4 positionOS   : POSITION;
            };

            struct TessellationControlPoint
            {
                float4 positionOS              : INTERNALTESSPOS;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct TessellationFactors
            {
                float tessFactor[3]    : SV_TessFactor;
                float insideTessFactor : SV_InsideTessFactor;
            };

            struct GeometryPoint
            {
                float4 positionCS   : SV_POSITION;
            };

            struct Varyings
            {
                float4 positionCS               : SV_POSITION;
                float2 barycentricCoordinates   : TEXCOORD0;
            };

            TessellationControlPoint Vert(Attributes input)
            {
                TessellationControlPoint output;
                output.positionOS = input.positionOS;
                return output;
            }

            float TessellationEdgeFactor(float3 p0, float3 p1) 
            {
                float edgeLength = distance(p0, p1);

                float3 edgeCenter = (p0 + p1) * 0.5;
                float viewDistance = distance(edgeCenter, _WorldSpaceCameraPos);

                return edgeLength * _ScreenParams.y / (_TessellationEdgeLength * viewDistance);
            }

            TessellationFactors PatchConstantFunction (InputPatch<TessellationControlPoint, 3> input)
            {
                float3 p0 = TransformObjectToWorld(input[0].positionOS.xyz);
                float3 p1 = TransformObjectToWorld(input[1].positionOS.xyz);
                float3 p2 = TransformObjectToWorld(input[2].positionOS.xyz);
                TessellationFactors output = (TessellationFactors)0;
                output.tessFactor[0] = TessellationEdgeFactor(p1, p2);
                output.tessFactor[1] = TessellationEdgeFactor(p2, p0);
                output.tessFactor[2] = TessellationEdgeFactor(p0, p1);
                output.insideTessFactor = (TessellationEdgeFactor(p1, p2) + 
                                            TessellationEdgeFactor(p2, p0) + 
                                            TessellationEdgeFactor(p0, p1)) * (1 / 3.0);
                return output;
            }

            [domain("tri")]
            [outputcontrolpoints(3)]
            [outputtopology("triangle_cw")]
            [partitioning("fractional_odd")]
            [patchconstantfunc("PatchConstantFunction")]
            TessellationControlPoint Hull(
                InputPatch<TessellationControlPoint, 3> input,
                uint id : SV_OutputControlPointID
            ) {
                return input[id];
            }

            [domain("tri")]
            GeometryPoint Domain(
                TessellationFactors factors,
                OutputPatch<TessellationControlPoint, 3> input,
                float3 barycentricCoordinates : SV_DomainLocation)
            {
                GeometryPoint output;
                float fU = barycentricCoordinates.x;
                float fV = barycentricCoordinates.y;
                float fW = barycentricCoordinates.z;
                float4 positionOS = input[0].positionOS * fU + input[1].positionOS * fV + input[2].positionOS * fW;
                output.positionCS = TransformObjectToHClip(positionOS.xyz);
                return output;
            }

            [maxvertexcount(3)]
            void Geom(triangle GeometryPoint input[3], inout TriangleStream<Varyings> stream)
            {
                Varyings output[3];
                output[0].positionCS = input[0].positionCS;
                output[1].positionCS = input[1].positionCS;
                output[2].positionCS = input[2].positionCS;
                output[0].barycentricCoordinates = float2(1, 0);
                output[1].barycentricCoordinates = float2(0, 1);
                output[2].barycentricCoordinates = float2(0, 0);
                stream.Append(output[0]);
                stream.Append(output[1]);
                stream.Append(output[2]);
            }

            half4 Frag(Varyings input) : SV_TARGET
            {
                float3 barys;
                barys.xy = input.barycentricCoordinates;
                barys.z = 1 - barys.x - barys.y;
                float3 deltas = fwidth(barys);
                float3 smoothing = deltas * _WireframeSmoothing;
                float3 thickness = deltas * _WireframeThickness;
                barys = smoothstep(thickness, thickness + smoothing, barys);
                float minBary = min(barys.x, min(barys.y, barys.z));
                return lerp(_WireframeColor, float4(1, 1, 1, 1), minBary);
            }

            ENDHLSL
        }
    }
}
